package xyz.lwm.lazycat.beans;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.AllArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.lwm.lazycat.utility.StringUtil;

import java.lang.annotation.Annotation;
import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;

/**
 * BeanContext
 *
 * @author lwm
 */
@AllArgsConstructor
class BeanContext implements Beans {

    // group by type
    private final Cache<String, Map<String, Object>> cache = Caffeine.newBuilder()
            .initialCapacity(200).expireAfterAccess(Duration.ofHours(1)).build();

    // bean ref manager
    private final BeanRefs beanRefs;

    @NotNull
    @Override
    @SuppressWarnings("unchecked")
    public <T> T get(@NotNull String beanName) throws BeansException {
        BeanRef<?> ref = beanRefs.get(beanName);
        if (ref == null) {
            throw new BeansException("bean name not found: " + beanName);
        }
        return (T) ref.getBean();
    }

    @NotNull
    @Override
    public <T> T get(@NotNull Class<T> clz) throws BeansException {
        Collection<T> values = map(clz).values();
        if (values.isEmpty()) {
            throw new BeansException("bean type not found: " + clz.getName());
        } else if (values.size() > 1) {
            throw new BeansException(StringUtil.format("bean type not unique, " +
                    "there are {} beans for {}.", values.size(), clz.getName()));
        } else {
            return values.iterator().next();
        }
    }

    @NotNull
    @Override
    @SuppressWarnings("unchecked")
    public <T> Map<String, T> map(@NotNull Class<T> clz) {
        return (Map<String, T>) Objects.requireNonNull(cache.get(clz.getName(), k ->
                beanRefs.map(ref -> clz.isAssignableFrom(ref.getDesign().getType()), BeanRef::getBean)));
    }

    @NotNull
    @Override
    @SuppressWarnings("unchecked")
    public <T, A extends Annotation> Map<String, T> map(@NotNull Class<T> clz, @NotNull Class<A> aClz) {
        return (Map<String, T>) Objects.requireNonNull(cache.get(clz.getName() + ":" + aClz.getName(), k ->
                beanRefs.map(ref -> clz.isAssignableFrom(ref.getDesign().getType()) &&
                        ref.getDesign().getAnnotations().hasAnnotation(aClz), BeanRef::getBean)));
    }

    @NotNull
    @Override
    @SuppressWarnings("unchecked")
    public <T, A extends Annotation> Map<String, T> annotatedMap(@NotNull Class<A> aClz) {
        return (Map<String, T>) Objects.requireNonNull(cache.get(aClz.getName(), k ->
                beanRefs.map(ref -> ref.getDesign().getAnnotations().hasAnnotation(aClz), BeanRef::getBean)));
    }

}
